查看原文
其他

ASP.NET Core实现Http自定义请求头策略

DotNet 2021-09-23

(给DotNet加星标,提升.Net技能

转自:lex-wu
cnblogs.com/lex-wu/p/13301272.html

前言


在正常的情况下,当我们系统用到JWT认证方式时,需要在Http请求头添加Authorization: XXX,这样在后台服务的控制器中打上[Authorize]授权标签,就限定所有的请求必须通过鉴权方可访问。


在ASP.NET Core - 基于IHttpContextAccessor实现系统级别身份标识这篇文章中我们能够注意到,通过IHttpContextAccessor获取基于请求生成的HttpContext后,我们是能够拿到基于该次请求的所有http信息的。这就给予了我们可以基于Http的请求做自定义请求头的策略来满足我们业务的扩展。


场景


在不少的场景中,特别是多租户的场景中,我们是需要知道当前是哪个租户下的用户在操作,而我们又不需要把这租户作为业务参数传递,毕竟如果按照业务参数这样的方式去实现的话,就好比从源头传入了一个污染源,需要一路污染下去,这是非常不推荐的方式,这时就可以利用自定义http请求头来传递这个参数,在最终需要用到这个参数的时候获取出来。


在我们的系统层面,例如我们的应用部署在K8S集群中的网络,在网关层解析了JWT通过认证后放行之后,内网的应用服务可以依赖内网的保护把JWT的认证删减掉(这样在一定程上是可以提高性能的),我们就可以通过以下自定义头的实现方式,把租户信息携带在http头部,一路传递下去。


实现


类似于我们在【ASP.NET Core - 基于IHttpContextAccessor实现系统级别身份标识】中的IHttpContextAccessor实现方式一样,我们先定义我们的IHttpHeaderAccessor用来获取HttpHeader


/// <summary>
/// httpcontext的header
/// </summary>
public interface IHttpHeaderAccessor
{
/// <summary>
/// the httpheader
/// </summary>
IHeaderDictionary HttpHeader { get; }
}


HttpHeaderAccessor 的实现  


/// <summary>
/// httpheader访问器
/// </summary>
public class HttpHeaderAccessor : IHttpHeaderAccessor
{
/// <summary>
/// the HttpContextAccessor
/// </summary>
private readonly IHttpContextAccessor _httpContextAccessor;
public IHeaderDictionary HttpHeader => _httpContextAccessor.HttpContext.Request.Headers;

/// <summary>
/// ctor
/// </summary>
///<param name="httpContextAccessor">the HttpContextAccessor</param>
public HttpHeaderAccessor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
}


定义http请求头的自定义参数


public interface ICustomHeaderAccessor
{
/// <summary>
/// 租户Id
/// </summary>
string TenantId { get; }
}


http请求头的自定义参数的获取,这里是直接从头部获取到租户信息


/// <summary>
/// 获取自定义http头
/// </summary>
public class CustomHeaderAccessor : ICustomHeaderAccessor
{
protected IHttpHeaderAccessor _httpHeaderAccessor { get; }
/// <summary>
/// ctor
/// </summary>
/// <param name="httpHeaderAccessor"></param>
public CustomHeaderAccessor(IHttpHeaderAccessor httpHeaderAccessor)
{
_httpHeaderAccessor = httpHeaderAccessor;
}

///<summary>
/// 租户Id
/// </summary>
public string TenantId
{
get
{
return _httpHeaderAccessor.HttpHeader[HeaderConst.TenantId];
}
}
}


扩展


在ASP.NET Core - 基于IHttpContextAccessor实现系统级别身份标识 我们知道IHttpContextAccessor,IHttpHeaderAccessor,ICustomHeaderAccessor都需要注册,有了上面的这些方法定义,我们定义一个ServiceCollection的扩展方法进行注册


/// <summary>
/// 基于IServiceCollection的扩展类
/// </summary>
public static class ServiceCollectionExtension
{
/// <summary>
///注册IHttpContextAccessor,IHttpHeaderAccessor和ICustomHeaderAccessor
/// </summary>
/// <param name="services">the IServiceCollection</param>
/// <returns></returns>
public static IServiceCollection AddCustomHttpHeader(this IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IHttpHeaderAccessor, HttpHeaderAccessor>();
services.AddSingleton<ICustomHeaderAccessor, CustomHeaderAccessor>();
return services;
}
}


有了服务的注册,我们还需要一个当前服务的实例提供者,一个ServiceProvider,如下


public class ServiceProviderInstance
{
public static IServiceProvider Instance { get; set; }
}


我们在获取到当前进程的ServiceProvider后,保存到本地静态变量


/// <summary>
/// 基于IApplicationBuilder的扩展
/// </summary>
public static class ServiceProviderExtension
{
/// <summary>
/// 给ServiceProviderInstance赋值ServiceProvider实例
/// </summary>
/// <param name="applicationBuilder">the IApplicationBuilder</param>
/// <returns></returns>
public static IApplicationBuilder UseServiceProviderBulider(this IApplicationBuilder applicationBuilder)
{
ServiceProviderInstance.Instance = applicationBuilder.ApplicationServices;
return applicationBuilder;
}
}


这里为什么能够在进程内保存一个静态的ServiceProvider?


是因为在应用启动过程中,已经把Ioc容器构建好,把该注册的对象实例关系都已经保存在Ioc容器中了,这时ServiceProvider在进程内是不会有变化的了(动态注册暂时没在本篇文章的使用范围)。


在这里已经给我们提示了另一种对象实例获取的方式,就是通过这个本地的ServiceProviderInstance来解析自己的所注册的对象实例,而不仅仅是通过构造函数(或者属性)注入的方式。


使用


看看我们如何完成整个流程的注册以及使用。


首选需要在程序启动的时候注册。这里通过注册以及swagger的声明,用来做为本地的调试请求头添加


public void ConfigureServices(IServiceCollection services)
{
// 注册Swagger
services.AddAppSwagger(document =>
{
document.Description = "API";
document.OperationProcessors.Add(new OperationSecurityScopeProcessor("TenantId"));
document.DocumentProcessors.Add(new SecurityDefinitionAppender("TenantId", new NSwag.OpenApiSecurityScheme
{
Type = NSwag.OpenApiSecuritySchemeType.ApiKey,
Name = "TenantId",
In = NSwag.OpenApiSecurityApiKeyLocation.Header,
Description = "租户Id"
}));
services.AddHttpHeader();
}


获取系统的ServiceProvider并保存到本地。


public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseServiceProviderBuilder();
// Swagger中间件
app.UseAppSwagger();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}


定义应用服务的抽象类


public abstract class ServiceBase
{
/// <summary>
/// 自定义头信息
/// </summary>
protected ICustomHeaderAccessor CustomHeader{ get; set; }

/// <summary>
/// ctor
/// </summary>
protected ServiceBase ()
{
CustomHeader = ServiceProviderInstance.Instance.GetRequiredService<ICustomHeaderAccessor>();
}
}


使用层面上的赋值


public class TestService : ServiceBase,ITestService
{
private readonly IRepository<Product> _productRepository;
public TestService(IRepository<Product> productRepository)
{
_productRepository = productRepository;
}
public Result AddProduct(ProductDto dto)
{
_productRepository.Insert(new Product
{
Name = dto.Name,
...
TenantId = CustomHeader.TenantId
});
}
}


Swagger的本地调用


为了方便调试,我们在上面的注册代码中写入了通过swagger中的请求头携带的http头部请求信息,在每次的本地调用中,可通过以下方式把我们需要传递的自定义信息填充进去。



- EOF -



推荐阅读  点击标题可跳转

.NET Core WebApi使用JWT验证签名

Windows 下部署 .NET Core 到 Docker

.NET Core项目中使用SkyWalkingAPM踩坑排坑日记


看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

好文章,我在看❤️

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存